package cn.iocoder.yudao.framework.common.util.ipa;

import com.dd.plist.*;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;
import org.xml.sax.InputSource;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Slf4j
public class IconReplacer {
    private final Path ipaPath;
    private final Path newIconPath;
    private final Path tempDir;

//    public static void main(String[] args) throws Exception {
//        IconReplacer iconReplacer = new IconReplacer(
//                "/Users/liujq/Downloads/fdf8deac-9fbb-4ec9-b3ef-884513f80882.ipa",
//                "/Users/liujq/Downloads/eefec4ea-e42f-40d9-972d-5ba012e62bec.png");
//        try {
//            iconReplacer.replaceIconAddDyLib("1111");
//        } catch (Exception e) {
//            log.error("替换图标失败", e);
//        }
//        File infoPlistFile = new File("C:\\Users\\liujq\\Downloads\\Payload\\AMapiPhone.app\\Infow.plist");
//        NSDictionary rootDict = (NSDictionary) PropertyListParser.parse(infoPlistFile);
//        log.info("rootDict: " + rootDict);
//
//        // 保存Info.plist
//        PropertyListParser.saveAsXML(rootDict, infoPlistFile);
//        iconReplacer.postProcessXMLFile(infoPlistFile);
//
//        String xml = iconReplacer.removeCDataAndEscape( "");
//        log.info("xml: {}", xml);
//    }

    public IconReplacer(String ipaPath, String newIconPath) {
        this.ipaPath = Paths.get(ipaPath);
        this.newIconPath = Paths.get(newIconPath);
        this.tempDir = Paths.get("temp_ipa_" + System.currentTimeMillis());
    }

    public static void main(String[] args) throws Exception{
        IconReplacer iconReplacer = new IconReplacer(
                "/Users/liujq/Downloads/fdf8deac-9fbb-4ec9-b3ef-884513f80882.ipa",
                "/Users/liujq/Downloads/eefec4ea-e42f-40d9-972d-5ba012e62bec.png");
        File infoPlistFile = new File("C:\\Users\\Administrator\\xwechat_files\\wxid_4qjp6xqz5cpw22_7a6b\\msg\\file\\2025-09\\info.plist");
        NSDictionary rootDict = (NSDictionary) PropertyListParser.parse(infoPlistFile);


        // 保存Info.plist
        PropertyListParser.saveAsXML(rootDict, infoPlistFile);
        iconReplacer.postProcessXMLFile(infoPlistFile);


    }

//    public void replaceIcon() throws Exception {
//        try {
//            unzipIpa();
//            Path appDirectory = findAppDirectory();
//            File appDir = appDirectory.toFile();
//            File infoPlistFile = new File(appDir, "Info.plist");
//            NSDictionary rootDict = (NSDictionary) PropertyListParser.parse(infoPlistFile);
//            List<String> iconPrefixes = getIconFileNames(rootDict);
//            findIconFiles(appDir.getAbsolutePath(), iconPrefixes)
//                    .forEach(iconFile -> {
//                        try {
//                            log.info("正在替换图标: " + iconFile);
//                            Files.copy(newIconPath, Paths.get(iconFile), StandardCopyOption.REPLACE_EXISTING);
//                        } catch (IOException e) {
//                            throw new UncheckedIOException(e);
//                        }
//                    });
//
//            repackageIpa();
//        } finally {
//            cleanUp();
//        }
//    }

    public void replaceIcon() throws Exception {
        try {
            unzipIpa();
            Path appDirectory = findAppDirectory();
            File appDir = appDirectory.toFile();

            // 1. 删除所有旧图标
            deleteAppIconFiles(appDir);

            File infoPlistFile = new File(appDir, "Info.plist");
            NSDictionary rootDict = (NSDictionary) PropertyListParser.parse(infoPlistFile);

            // 2. 清理旧配置
            removeUnusedIconKeys(rootDict);

            // 3. 复制并生成三个图标文件
            List<String> newIconNames = generateIconFiles(appDir);

            // 4. 更新CFBundleIconFiles数组
            updateCFBundleIconFiles(rootDict, newIconNames);

            // 保存Info.plist
            PropertyListParser.saveAsXML(rootDict, infoPlistFile);
            postProcessXMLFile(infoPlistFile);

            repackageIpa(infoPlistFile);
        } finally {
            cleanUp();
        }
    }

    public void replaceIconAddDyLib(String checkUrl) throws Exception {
        try {
            unzipIpa();
            Path appDirectory = findAppDirectory();
            File appDir = appDirectory.toFile();

            // 1. 删除所有旧图标
            deleteAppIconFiles(appDir);

            File infoPlistFile = new File(appDir, "Info.plist");
            NSDictionary rootDict = (NSDictionary) PropertyListParser.parse(infoPlistFile);

            // 2. 清理旧配置
            removeUnusedIconKeys(rootDict);

            // 3. 复制并生成三个图标文件
            List<String> newIconNames = generateIconFiles(appDir);

            // 4. 更新CFBundleIconFiles数组
            updateCFBundleIconFiles(rootDict, newIconNames);

            // 保存Info.plist
            PropertyListParser.saveAsXML(rootDict, infoPlistFile);
            postProcessXMLFile(infoPlistFile);

            Path zsignFile = appDirectory.resolve("zsign.txt");
            Files.write(zsignFile, checkUrl.getBytes(StandardCharsets.UTF_8));

            repackageIpa(infoPlistFile);
        } finally {
            cleanUp();
        }
    }


    private List<String> generateIconFiles(File appDir) throws IOException {
        List<String> iconNames = Arrays.asList("icon.png", "icon@2x.png", "icon@3x.png");

        // 将同一个图标复制为三个不同文件名
        for (String name : iconNames) {
            Path target = appDir.toPath().resolve(name);
            Files.copy(newIconPath, target, StandardCopyOption.REPLACE_EXISTING);
        }
        return iconNames;
    }

    private void updateCFBundleIconFiles(NSDictionary rootDict, List<String> iconNames) {
        // 将字符串列表转换为NSObject数组
        NSObject[] nsObjects = iconNames.stream()
                .map(name -> new NSString(name))
                .toArray(NSObject[]::new);

        NSArray iconFiles = new NSArray(nsObjects);
        rootDict.put("CFBundleIconFiles", iconFiles);
    }


    private void deleteAppIconFiles(File appDir) throws IOException {
        Files.walk(appDir.toPath())
                .filter(path -> path.getFileName().toString().startsWith("AppIcon")
                        && path.toString().toLowerCase().endsWith(".png"))
                .forEach(path -> {
                    try {
                        log.info("删除旧图标: {}", path);
                        Files.delete(path);
                    } catch (IOException e) {
                        log.error("删除文件失败: {}", path, e);
                    }
                });
    }

    private void removeUnusedIconKeys(NSDictionary rootDict) {
        rootDict.remove("CFBundleIcons");
        rootDict.remove("CFBundleIcons~ipad");
    }

    private String copyAndRenameIcon(File appDir) throws IOException {
        String newName = "icon.png"; // 统一重命名为icon.png
        Path target = appDir.toPath().resolve(newName);
        Files.copy(newIconPath, target, StandardCopyOption.REPLACE_EXISTING);
        return newName;
    }

    private void updateCFBundleIconFiles(NSDictionary rootDict, String iconName) {
        // 创建NSObject数组（支持多图标扩展）
        List<NSObject> iconObjects = new ArrayList<>();
        iconObjects.add(new NSString(iconName));

        // 构造NSArray并更新字典
        NSArray iconFiles = new NSArray(iconObjects.toArray(new NSObject[0]));
        rootDict.put("CFBundleIconFiles", iconFiles);
    }

    // ----------- 核心方法：递归转义所有字符串 -----------
    private static void escapeAllStrings(NSObject node) {
        if (node instanceof NSDictionary) {
            NSDictionary dict = (NSDictionary) node;
            for (String key : dict.allKeys()) {
                NSObject value = dict.get(key);
                escapeAllStrings(value); // 递归处理值
                if (value instanceof NSString) {
                    // 转义字符串并更新字典
                    String escaped = escapeXML(value.toString());
                    dict.put(key, new NSString(escaped));
                }
            }
        } else if (node instanceof NSArray) {
            NSArray array = (NSArray) node;
            for (int i = 0; i < array.count(); i++) {
                NSObject item = array.objectAtIndex(i);
                escapeAllStrings(item); // 递归处理数组项
                if (item instanceof NSString) {
                    // 转义字符串并更新数组
                    String escaped = escapeXML(item.toString());
                    array.setValue(i, new NSString(escaped));
                }
            }
        }
        // 其他类型（NSNumber、NSDate等）无需处理
    }

    // ----------- XML 特殊字符转义 -----------
    private static String escapeXML(String input) {
        return input.replace("&", "&amp;")
                .replace("<", "&lt;")
                .replace(">", "&gt;")
                .replace("\"", "&quot;")
                .replace("'", "&apos;");
    }

    // ----------- 后处理移除残留 CDATA（备用方案）-----------
    private void postProcessXMLFile(File plistFile) throws Exception {
        String content = new String(Files.readAllBytes(plistFile.toPath()), StandardCharsets.UTF_8);
        content = removeCDataAndEscape(content);
        log.info("写入文件: {}", content);
        Files.write(plistFile.toPath(), content.getBytes(StandardCharsets.UTF_8));
        // 读取文件内容
        String content2 = new String(Files.readAllBytes(plistFile.toPath()), StandardCharsets.UTF_8);
        log.info("读取的文件内容: {}", content2);

    }

    public String removeCDataAndEscape(String xml) throws Exception {
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        // 禁用外部 DTD 获取和实体解析
        factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
        factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
        factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
        factory.setXIncludeAware(false);
        factory.setExpandEntityReferences(false);
        
        DocumentBuilder builder = factory.newDocumentBuilder();
        Document doc = builder.parse(new InputSource(new java.io.StringReader(xml)));

        processNode(doc.getDocumentElement()); // 递归处理所有节点

        return documentToString(doc);
    }

    private static void processNode(Node node) {
        if (node.getNodeType() == Node.CDATA_SECTION_NODE) {
            // 将CDATA节点替换为文本节点
            Text textNode = node.getOwnerDocument().createTextNode(node.getTextContent());
            node.getParentNode().replaceChild(textNode, node);
        } else if (node.getNodeType() == Node.ELEMENT_NODE && "string".equals(node.getNodeName())) {
            // 特殊处理string元素，确保空字符串也被保留
            if (!node.hasChildNodes() || (node.getChildNodes().getLength() == 0)) {
                // 为空的string元素添加一个空的文本节点，确保输出<string></string>而不是自闭合标签
                Text emptyTextNode = node.getOwnerDocument().createTextNode("");
                node.appendChild(emptyTextNode);
            } else {
                // 递归处理子节点
                NodeList children = node.getChildNodes();
                for (int i = 0; i < children.getLength(); i++) {
                    processNode(children.item(i));
                }
            }
        } else if (node.hasChildNodes()) {
            // 递归处理子节点
            NodeList children = node.getChildNodes();
            for (int i = 0; i < children.getLength(); i++) {
                processNode(children.item(i));
            }
        }
    }

    private static String documentToString(Document doc) throws TransformerException {
        TransformerFactory tf = TransformerFactory.newInstance();
        Transformer transformer = tf.newTransformer();
        transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
        transformer.setOutputProperty(OutputKeys.INDENT, "yes");
        transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "1");
        // 确保空元素不使用自闭合标签
        transformer.setOutputProperty(OutputKeys.METHOD, "xml");
        StringWriter writer = new StringWriter();
        transformer.transform(new DOMSource(doc), new StreamResult(writer));
        return writer.toString();
    }


    // 修改为返回所有图标前缀列表
    private static List<String> getIconFileNames(NSDictionary rootDict) {
        Set<String> iconNames = new LinkedHashSet<>();

        try {
            NSDictionary iconsDict = (NSDictionary) rootDict.objectForKey("CFBundleIcons");
            if (iconsDict != null) {
                NSDictionary primaryIconsDict = (NSDictionary) iconsDict.objectForKey("CFBundlePrimaryIcon");
                if (primaryIconsDict != null) {
                    NSArray iconFilesArray = (NSArray) primaryIconsDict.objectForKey("CFBundleIconFiles");
                    if (iconFilesArray != null) {
                        for (int i = 0; i < iconFilesArray.count(); i++) {
                            iconNames.add(iconFilesArray.objectAtIndex(i).toString());
                        }
                    }
                }
            }
        } catch (Exception e) {
            log.warn("从CFBundleIcons获取图标失败", e);
        }

        try {
            NSArray iconFiles = (NSArray) rootDict.objectForKey("CFBundleIconFiles");
            if (iconFiles != null) {
                for (int i = 0; i < iconFiles.count(); i++) {
                    iconNames.add(iconFiles.objectAtIndex(i).toString());
                }
            }
        } catch (Exception e) {
            log.warn("从CFBundleIconFiles获取图标失败", e);
        }

        try {
            NSObject iconFile = rootDict.objectForKey("CFBundleIconFile");
            if (iconFile != null) {
                iconNames.add(iconFile.toString());
            }
        } catch (Exception e) {
            log.warn("从CFBundleIconFile获取图标失败", e);
        }

        return new ArrayList<>(iconNames);
    }

    // 修改以支持多前缀匹配
    public static List<String> findIconFiles(String appDir, List<String> iconFileNamePrefixes) {
        File appDirectory = new File(appDir);
        List<String> iconFilesList = new ArrayList<>();
        if (appDirectory.isDirectory()) {
            for (String prefix : iconFileNamePrefixes) {
                File[] iconFiles = appDirectory.listFiles((dir, name) ->
                        name.startsWith(prefix) && (name.toLowerCase().endsWith(".png")));
                if (iconFiles != null) {
                    Arrays.stream(iconFiles)
                            .map(File::getAbsolutePath)
                            .forEach(iconFilesList::add);
                }
            }
        }
        return iconFilesList.isEmpty() ? Lists.newArrayList() : iconFilesList;
    }

    private static String getIconFromCFBundleIcons(NSDictionary rootDict) {
        try {
            NSDictionary iconsDict = (NSDictionary) rootDict.objectForKey("CFBundleIcons");
            if (iconsDict != null) {
                NSDictionary primaryIconsDict = (NSDictionary) iconsDict.objectForKey("CFBundlePrimaryIcon");
                if (primaryIconsDict != null) {
                    NSArray iconFilesArray = (NSArray) primaryIconsDict.objectForKey("CFBundleIconFiles");
                    if (iconFilesArray != null && iconFilesArray.count() > 0) {
                        return iconFilesArray.objectAtIndex(iconFilesArray.count() - 1).toString();
                    }
                }
            }
        } catch (Exception e) {
            log.warn("从CFBundleIcons获取图标失败", e);
        }
        return null;
    }

    private static String getIconFromCFBundleIconFiles(NSDictionary rootDict) {
        try {
            NSArray iconFiles = (NSArray) rootDict.objectForKey("CFBundleIconFiles");
            if (iconFiles != null && iconFiles.count() > 0) {
                return iconFiles.objectAtIndex(iconFiles.count() - 1).toString();
            }
        } catch (Exception e) {
            log.warn("从CFBundleIconFiles获取图标失败", e);
        }
        return null;
    }

    private static String getIconFromCFBundleIconFile(NSDictionary rootDict) {
        try {
            NSObject iconFile = rootDict.objectForKey("CFBundleIconFile");
            if (iconFile != null) {
                return iconFile.toString();
            }
        } catch (Exception e) {
            log.warn("从CFBundleIconFile获取图标失败", e);
        }
        return null;
    }

    public static List<String> findIconFiles(String appDir, String iconFileNamePrefix) {
        File appDirectory = new File(appDir);
        if (appDirectory.isDirectory()) {
            File[] iconFiles = appDirectory.listFiles((dir, name) ->
                    name.startsWith(iconFileNamePrefix) && name.endsWith(".png"));
            if (iconFiles != null && iconFiles.length > 0) {
                return Arrays.stream(iconFiles)
                        .map(File::getAbsolutePath)
                        .collect(Collectors.toList());
            }
        }
        return Lists.newArrayList();
    }

    public static String getIconFileName(NSDictionary rootDict) {
        String iconName = getIconFromCFBundleIcons(rootDict);
        if (iconName != null) {
            return iconName;
        }

        iconName = getIconFromCFBundleIconFiles(rootDict);
        if (iconName != null) {
            return iconName;
        }

        return getIconFromCFBundleIconFile(rootDict);
    }

    private void unzipIpa() throws IOException {
        log.info("开始解压IPA文件: {}", ipaPath);
        try {
            // 创建临时目录
            Files.createDirectories(tempDir);

            // 使用系统unzip命令
            ProcessBuilder pb = new ProcessBuilder("unzip", "-q", ipaPath.toString(), "-d", tempDir.toString());
            pb.redirectErrorStream(true);
            Process process = pb.start();
            log.info("执行命令: {}", String.join(" ", pb.command()));
            // 读取命令输出
//            try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
//                String line;
//                while ((line = reader.readLine()) != null) {
//                    log.debug("unzip output: {}", line);
//                }
//            }

            if (!process.waitFor(60, TimeUnit.SECONDS)) {
                process.destroyForcibly();
                throw new IOException("解压超时");
            }
            log.info("IPA文件解压完成");
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new IOException("解压被中断", e);
        }
    }

    private Path findAppDirectory() throws IOException {
        Path payloadDir = tempDir.resolve("Payload");
        try (DirectoryStream<Path> stream = Files.newDirectoryStream(payloadDir)) {
            for (Path path : stream) {
                if (path.toString().endsWith(".app")) {
                    return path;
                }
            }
        }
        throw new IOException("找不到.app目录");
    }

    private void repackageIpa(File infoPlistFile) throws IOException {
        log.info("开始重新打包IPA文件");
        String newFileName = "new_" + ipaPath.getFileName();
        Path newIpaPath = ipaPath.getParent().resolve(newFileName);

        try {
            // 切换到临时目录并执行zip命令
            ProcessBuilder pb = new ProcessBuilder("zip", "-qr", newIpaPath.toString(), ".");
            pb.directory(tempDir.toFile());
            pb.redirectErrorStream(true);
            Process process = pb.start();

            // 读取命令输出
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
                String line;
                while ((line = reader.readLine()) != null) {
                    log.debug("zip output: {}", line);
                }
            }

            int exitCode = process.waitFor();
            if (exitCode != 0) {
                throw new IOException("打包失败，退出码: " + exitCode);
            }

            // 替换原文件
            Files.move(newIpaPath, ipaPath, StandardCopyOption.REPLACE_EXISTING);
            // 读取文件内容
            String content2 = new String(Files.readAllBytes(infoPlistFile.toPath()), StandardCharsets.UTF_8);
            log.info("打包后读取的文件内容: {}", content2);
            log.info("IPA文件重新打包完成");
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new IOException("打包被中断", e);
        }
    }

    private void cleanUp() {
        log.info("开始清理临时文件");
        try {
            if (Files.exists(tempDir)) {
                Files.walk(tempDir)
                        .sorted(Comparator.reverseOrder())
                        .forEach(path -> {
                            try {
                                Files.delete(path);
                            } catch (IOException e) {
                                log.warn("删除临时文件失败: " + path);
                            }
                        });
            }
            log.info("临时文件清理完成");
        } catch (IOException e) {
            log.error("清理临时目录失败", e);
        }
    }
}